/*
 * main.cpp
 * 
 * Copyright 2013 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */

#define VERSION "2.0.1"

#include <string>
#include <fstream>
#include <vector>
#include <fstream>
#include <set>
#include <map>
#include <libsmbclient.h>
#include <iostream>
#include <cassert>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <ftplib.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sstream>
using namespace std;
#include "baza.h"

#define MAKSYMALNA_ILOSC_PROB 1
#define MAKSYMALNY_POZIOM_KATALOGU 20

#define PDEBUG(format, ...) printf(format"\n", ## __VA__ARGS__);

struct Wyjatek {
	string opis;
	Wyjatek(const string & opis):opis(opis) {
}};

class WyjatekSkanera:public Wyjatek {
 public:
	WyjatekSkanera():Wyjatek("Przerwane skanowanie\n") {
}};

class ISkaner {
 public:
	struct Plik {
		std::string nazwa;
		unsigned long long rozmiar;
		bool katalog;
	};

	typedef vector < Plik > Pliki;

	virtual ~ISkaner() {
	};
	virtual bool Polacz(const string &komputer) = 0;
	virtual Pliki PobierzPliki(const string &aktualny_katalog) = 0;
	virtual bool PolaczPonownie() = 0;
	virtual bool chdir(string &aktualny_katalog) = 0;
};


static string nazwa_komputera;
static IBaza *baza = 0;	
static ofstream plik_log;
ISkaner *skaner = 0;


void Zaloguj(ostream & plik, string tekst)
{
	cout << nazwa_komputera << " : " << tekst << endl;
	plik << nazwa_komputera << " : " << tekst << endl;
}
void ZalogujBlad(ostream & plik, string tekst)
{
	cerr << nazwa_komputera << " : " << tekst << "errno: " << strerror(errno) << endl;
	plik << nazwa_komputera << " : " << tekst << "errno: " << strerror(errno) << endl;
}

static void safe_exit()
{
	if ( baza )
		delete baza;
	if ( skaner )
		delete skaner;
	plik_log.close();
	exit(0);
}
static void sig_hup(int a)
{
	Zaloguj(plik_log, "Cought signal. Shutting down");
	safe_exit();
}

char workgroup[9];
char username[6];
char password[2];
void smb_auth_fn(const char *server, const char *share, char *wrkgrp, int wrkgrplen, char *user, int userlen, char *passwd, int passwdlen)
{
	strncpy(wrkgrp, workgroup, wrkgrplen - 1);
	wrkgrp[wrkgrplen - 1] = 0;
	strncpy(user, username, userlen - 1);
	user[userlen - 1] = 0;
	strncpy(passwd, password, passwdlen - 1);
	passwd[passwdlen - 1] = 0;
}
class SkanerNetBios:public ISkaner {
 protected:
	string komputer;
	unsigned poziom;
	int fd;
	bool polaczony;
	SMBCCTX *ctx;
	ostream & plik;

	
 public:
	
	SkanerNetBios(ostream &plik_log):plik(plik_log) {
		strcpy(workgroup, "workgroup");
		strcpy(username, "anonymous");
		strcpy(password, "");
		polaczony = false;
	} ~SkanerNetBios() {
		Rozlacz();
	}

	bool Polacz(const string &komputer) {
		this->komputer = komputer;
		poziom = 0;
		
		smbc_init(smb_auth_fn, 0); // sambna libratry init stuff

		if ((fd = smbc_opendir(((string) "smb://" + komputer + (string) "/").c_str())) < 0) {
			polaczony = false;
			Zaloguj(plik, "Nie udalo polaczyc po sambie: ");
			throw WyjatekSkanera();
			return polaczony;
		}
		polaczony = true;
		return polaczony;
	}

	void Rozlacz() {
		if (polaczony) {
			smbc_close(fd);
			polaczony = false;
		}
	}

	bool PolaczPonownie() {
		Rozlacz();
		return Polacz(this->komputer);
	}

	Pliki PobierzPliki(const string &aktualny_katalog) {
		Pliki wyniki;
		struct smbc_dirent *dirent;
		long long int size;
		unsigned  i;
		int poziom;
		
		// count the number of '/' to get poziom
		for (poziom = -1, i=0; i < aktualny_katalog.size(); i++)
			if ( aktualny_katalog[i] == '/' )
				poziom++;
		if ( poziom == -1 ) {
			Zaloguj(plik, "bledne dane \n");
			throw WyjatekSkanera();
			return wyniki;
		}
		
		if (poziom == 0) {
			
			while ((dirent = smbc_readdir(fd))) {
				if (dirent->smbc_type == SMBC_FILE_SHARE) {
					Plik wynik;
					i = 0;
					while (dirent->name[i] != '\0')
						i++;
					if (dirent->name[i - 1] == '$')
						continue;
					wynik.nazwa.assign(dirent->name);
					wynik.katalog = true;
					wynik.rozmiar = 0;
					wyniki.push_back(wynik);
				}
			}

			smbc_closedir(fd);
			return wyniki;
		}
		
		const string szukanie = (string) "smb://" + komputer + aktualny_katalog;
		if ( !(fd = smbc_opendir(szukanie.c_str())) ) {
			Zaloguj(plik, "Nie udalo sie otworzyc katalogu: " + szukanie);
			throw WyjatekSkanera();
			return wyniki;
		}
		while ((dirent = smbc_readdir(fd)) != NULL) {
			Plik wynik;
			struct stat st;
			
			st.st_size = 0;
			size = 0;
			if (!strcmp(dirent->name, "") || !strcmp(dirent->name, ".") || !strcmp(dirent->name, ".."))
				continue;	
			
			/* smbc_stat seems to be editing the string it has passed ... */
			string temp = szukanie + (string)dirent->name + (string)"\0";
			if (smbc_stat(temp.c_str(), &st) < 0) {
				Zaloguj(plik, "nie udalo sie pobrac rozmiaru pliku: " + temp);
			}
			 if (st.st_size < 0) {	
				size = (long long int)UINT_MAX - (long long int)st.st_size;
			} else {
				size = (long long int)st.st_size;
			}
			
			wynik.katalog = (dirent->smbc_type == SMBC_DIR) ? true : false;
			wynik.nazwa.assign(dirent->name);
			wynik.rozmiar = (unsigned long long int)size;
			{
				/* i have no idea why, but i get segfault when i not 
				 * move variables to the stos */
				Plik temp1;
				Pliki tempy;
				temp1 = wynik;
				tempy = wyniki;
				tempy.push_back(temp1);
				wyniki = tempy;
			}
		}
		smbc_closedir(fd);
		
		return wyniki;
	}
	bool chdir(string &aktualny_katalog) 
	{
		return true;
	}
};

class SkanerFTP:public ISkaner {
 protected:
	bool polaczony;
	netbuf *polaczenie;
	
	string komputer;
	
	enum OS_e { LINUX, WINDOWS };
	OS_e OS;
	
	ostream & plik; // plik_log
	
	char tmpfilename[30]; // flie name of temporary file
	int mkstemp_ret; // returner by mkstemp need to have it all the time
	FILE*fp;
 public:
 
	SkanerFTP(ostream & plik_log):plik(plik_log) {
		FtpInit();
		
		polaczenie = NULL;
		polaczony = false;
		
		
		// get a tmpfile and create it
		strcpy(tmpfilename, "/tmp/.qsearch.XXXXXX");
		mkstemp_ret = mkstemp(tmpfilename);
		fp = fopen(tmpfilename, "r");
	}
	~SkanerFTP() {
		fclose(fp);
		close(mkstemp_ret);
		remove(tmpfilename);
		Rozlacz();
	}

	bool Polacz(const string &komputer) {
		char buf[40];
		
		if (polaczony)
			Rozlacz();
			
		this->komputer = komputer;
		if (!(polaczony = FtpConnect(komputer.c_str(), &polaczenie))) {
			Zaloguj(plik,  "Nie udalo sie polaczyc po FTP:");
			throw WyjatekSkanera();
			return false;
		}
		if (!(polaczony = FtpLogin("anonymous", "", polaczenie))) {
			Zaloguj(plik, "Blad podczas logowania\n");
			FtpQuit(polaczenie);
			throw WyjatekSkanera();
			return false;
		}
		FtpOptions(FTPLIB_CONNMODE, FTPLIB_PASSIVE, polaczenie);
		FtpSysType(buf, sizeof(buf), polaczenie);
		if ( !strcasecmp(buf, "unix") )
			OS = LINUX;
		else
			OS = WINDOWS;
		return polaczony;
	}

	void Rozlacz() {
		if (polaczony)
			FtpQuit(polaczenie);
		polaczony = false;
		polaczenie = NULL;
	}

	bool PolaczPonownie() {
		Rozlacz();
		return Polacz(komputer);
	}

	Pliki PobierzPliki(const string &aktualny_katalog) {
		Pliki wyniki;
		Plik wynik;
		char name[254] = "";
		char buf[2000];
		long long int size = 0;
		
		if ( !polaczony ) return wyniki;
		
		rewind(fp);	
		FtpDir(tmpfilename, aktualny_katalog.c_str(), polaczenie);
		
		rewind(fp);
		while( fgets(buf, sizeof(buf), fp) ) {
			if ( buf[0] == 'd' || buf[0] == '-' ) { // linux
				sscanf(buf, "%*s%*d%*s%*s%lld%*s%*d%*s%*c %[^\n]s", &size, name);
				wynik.katalog = (buf[0] == 'd');
			} else if ( buf[0] >= '0' && buf[0] <= '9' ) { //windows
				char is_it[40];
				sscanf(buf, "%*s%*s%s %[^\n]s", is_it, name);
				if ( !strcmp(is_it, "<DIR>") ) {
					wynik.katalog = true;
				} else {
					wynik.katalog = false;
					size = atoll(is_it);
				}
			} else {
				continue; // other skladnia
			}
			
			if (!strcmp("", name) || !strcmp(".", name) || !strcmp("..", name))
				continue;
			
			wynik.rozmiar = (unsigned long long int)size < 0 ? 0 : size;
			wynik.nazwa.assign(name);
			{
				/* i have no idea why, but i get segfault when i not 
				 * move variables to the stos */
				Plik temp1;
				Pliki tempy;
				temp1 = wynik;
				tempy = wyniki;
				tempy.push_back(temp1);
				wyniki = tempy;
			}
		}
		return wyniki;
	}

	bool chdir(string &aktualny_katalog) {
		return FtpChdir(aktualny_katalog.c_str(), polaczenie);
	}
};

void _Skanuj(const string &protocol, string &aktualny_katalog,
	unsigned poziom_katalogu, ostream &plik_log, 
	ISkaner * skaner, IBaza * baza)
{
	const int maksymalna_ilosc_prob = MAKSYMALNA_ILOSC_PROB;
	int numer_proby = 0;
	bool sukces = false;
	ISkaner::Pliki pliki;
	unsigned i;
	if (poziom_katalogu >= MAKSYMALNY_POZIOM_KATALOGU)
		return;
	
	for(numer_proby=0, sukces=false; 
		!sukces && numer_proby < maksymalna_ilosc_prob; numer_proby++) {
		try {
			//skanuje dany katalog
			sukces = true;
			pliki = skaner->PobierzPliki(aktualny_katalog);
		} catch(WyjatekSkanera) {
			sukces = false;
			if (skaner->PolaczPonownie())
				break;
		}
	}
	if (!sukces)
		throw Wyjatek("Blad podczas pobierania listy plikow");

	for (i = 0; i < pliki.size(); i++) {
		if (!pliki[i].katalog) {
			//wpis jest plikiem
			//jezeli pozycja jest plikiem dodajemy go do bazy
			baza->DodajWpis(poziom_katalogu, protocol, nazwa_komputera, aktualny_katalog, 
				pliki[i].nazwa, pliki[i].rozmiar);
			continue;
		}

		// dodajemy katalog do bazy
		//cout << poziom_katalogu<< protocol << nazwa_komputera<<aktualny_katalog<<pliki[i].nazwa<<endl;
		baza->DodajWpis(poziom_katalogu, protocol, nazwa_komputera, 
			 aktualny_katalog, pliki[i].nazwa + '/', 0);

		//wchodzimy do tego katalogu
		aktualny_katalog += pliki[i].nazwa;
		aktualny_katalog += "/";
		
		// rekursywnie skanujemy katalog
		_Skanuj(protocol, aktualny_katalog, poziom_katalogu + 1, plik_log, skaner, baza);
		
		// wychodzimy z tego katalogu
		int pozycja = aktualny_katalog.size() - 2;
		while ((pozycja >= 0) && (aktualny_katalog[pozycja] != '/'))
			pozycja--;
		aktualny_katalog = aktualny_katalog.substr(0, pozycja + 1);

	}
}

void Skanuj(const string &protocol, const string &ip, ostream &plik_log, ISkaner *skaner)
{
	string aktualny_katalog = "/";
	// baza jest global variable
	try {
		if ( skaner->Polacz(ip) ) {
			if ( baza == 0 ) // nie otwieraj dwa razy
				baza = OtworzBazePlik(nazwa_komputera, plik_log);
			_Skanuj(protocol, aktualny_katalog, 0, plik_log, skaner, baza);
		}
	} catch(Wyjatek w) {
		ZalogujBlad(plik_log, w.opis.c_str());
	}
}

int main(int argc, char *argv[])
{
	string ip;
	
	nazwa_komputera.assign(argv[1]);
	ip.assign(argv[2]);
	
	string plik_log_str = "/tmp/.qsearch." + nazwa_komputera + ".log";
	plik_log.open(plik_log_str.c_str(), ios_base::trunc | ios_base::out);
	
	Zaloguj(plik_log, " -------------------------------------- start!! ");
	
	signal(SIGHUP, sig_hup);
	signal(SIGQUIT,sig_hup);
	signal(SIGTERM,sig_hup);
	signal(SIGINT,sig_hup);
	
	Zaloguj(plik_log, " -------------------------------------- start samba: ");
	skaner = new SkanerNetBios(plik_log);
	Skanuj("smb", ip, plik_log, skaner);
	delete skaner;
	Zaloguj(plik_log, " -------------------------------------- koniec samba: ");
	
	Zaloguj(plik_log, " -------------------------------------- start FTP: ");
	skaner = new SkanerFTP(plik_log);
	Skanuj("ftp", ip, plik_log, skaner);
	delete skaner;
	Zaloguj(plik_log, " -------------------------------------- koniec FTP: ");
	
	Zaloguj(plik_log, " -------------------------------------- KONIEC!! ");
	
	if ( baza ) {
		baza->flush();
		delete baza;
	}
	plik_log.close();
	return 0;
}
